// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('apl', function() {
    var builtInOps = {
      '.': 'innerProduct',
      '\\': 'scan',
      '/': 'reduce',
      '⌿': 'reduce1Axis',
      '⍀': 'scan1Axis',
      '¨': 'each',
      '⍣': 'power',
    };
    var builtInFuncs = {
      '+': ['conjugate', 'add'],
      '−': ['negate', 'subtract'],
      '×': ['signOf', 'multiply'],
      '÷': ['reciprocal', 'divide'],
      '⌈': ['ceiling', 'greaterOf'],
      '⌊': ['floor', 'lesserOf'],
      '∣': ['absolute', 'residue'],
      '⍳': ['indexGenerate', 'indexOf'],
      '?': ['roll', 'deal'],
      '⋆': ['exponentiate', 'toThePowerOf'],
      '⍟': ['naturalLog', 'logToTheBase'],
      '○': ['piTimes', 'circularFuncs'],
      '!': ['factorial', 'binomial'],
      '⌹': ['matrixInverse', 'matrixDivide'],
      '<': [null, 'lessThan'],
      '≤': [null, 'lessThanOrEqual'],
      '=': [null, 'equals'],
      '>': [null, 'greaterThan'],
      '≥': [null, 'greaterThanOrEqual'],
      '≠': [null, 'notEqual'],
      '≡': ['depth', 'match'],
      '≢': [null, 'notMatch'],
      '∈': ['enlist', 'membership'],
      '⍷': [null, 'find'],
      '∪': ['unique', 'union'],
      '∩': [null, 'intersection'],
      '∼': ['not', 'without'],
      '∨': [null, 'or'],
      '∧': [null, 'and'],
      '⍱': [null, 'nor'],
      '⍲': [null, 'nand'],
      '⍴': ['shapeOf', 'reshape'],
      ',': ['ravel', 'catenate'],
      '⍪': [null, 'firstAxisCatenate'],
      '⌽': ['reverse', 'rotate'],
      '⊖': ['axis1Reverse', 'axis1Rotate'],
      '⍉': ['transpose', null],
      '↑': ['first', 'take'],
      '↓': [null, 'drop'],
      '⊂': ['enclose', 'partitionWithAxis'],
      '⊃': ['diclose', 'pick'],
      '⌷': [null, 'index'],
      '⍋': ['gradeUp', null],
      '⍒': ['gradeDown', null],
      '⊤': ['encode', null],
      '⊥': ['decode', null],
      '⍕': ['format', 'formatByExample'],
      '⍎': ['execute', null],
      '⊣': ['stop', 'left'],
      '⊢': ['pass', 'right'],
    };

    var isOperator = /[\.\/⌿⍀¨⍣]/;
    var isNiladic = /⍬/;
    var isFunction = /[\+−×÷⌈⌊∣⍳\?⋆⍟○!⌹<≤=>≥≠≡≢∈⍷∪∩∼∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢]/;
    var isArrow = /←/;
    var isComment = /[⍝#].*$/;

    var stringEater = function(type) {
      var prev;
      prev = false;
      return function(c) {
        prev = c;
        if (c === type) {
          return prev === '\\';
        }
        return true;
      };
    };
    return {
      startState: function() {
        return {
          prev: false,
          func: false,
          op: false,
          string: false,
          escape: false,
        };
      },
      token: function(stream, state) {
        var ch, funcName;
        if (stream.eatSpace()) {
          return null;
        }
        ch = stream.next();
        if (ch === '"' || ch === "'") {
          stream.eatWhile(stringEater(ch));
          stream.next();
          state.prev = true;
          return 'string';
        }
        if (/[\[{\(]/.test(ch)) {
          state.prev = false;
          return null;
        }
        if (/[\]}\)]/.test(ch)) {
          state.prev = true;
          return null;
        }
        if (isNiladic.test(ch)) {
          state.prev = false;
          return 'niladic';
        }
        if (/[¯\d]/.test(ch)) {
          if (state.func) {
            state.func = false;
            state.prev = false;
          } else {
            state.prev = true;
          }
          stream.eatWhile(/[\w\.]/);
          return 'number';
        }
        if (isOperator.test(ch)) {
          return 'operator apl-' + builtInOps[ch];
        }
        if (isArrow.test(ch)) {
          return 'apl-arrow';
        }
        if (isFunction.test(ch)) {
          funcName = 'apl-';
          if (builtInFuncs[ch] != null) {
            if (state.prev) {
              funcName += builtInFuncs[ch][1];
            } else {
              funcName += builtInFuncs[ch][0];
            }
          }
          state.func = true;
          state.prev = false;
          return 'function ' + funcName;
        }
        if (isComment.test(ch)) {
          stream.skipToEnd();
          return 'comment';
        }
        if (ch === '∘' && stream.peek() === '.') {
          stream.next();
          return 'function jot-dot';
        }
        stream.eatWhile(/[\w\$_]/);
        state.prev = true;
        return 'keyword';
      },
    };
  });

  CodeMirror.defineMIME('text/apl', 'apl');
});
